/**
 * 
 * Ashish Tomar
 * IDS Project - 2
 * Port Pairing and Statistics Generator
 * 
 */

#include <pcap.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/if_ether.h> 
#include <map>
#include <utility>
#include <fstream>
#include <string>
#include <math.h>
using namespace std;


double current_time;
double start_time;
double diff_time;
int pkt_count;
long double buffer_total;
int currenthour;

/* tcpdump header (ether.h) defines ETHER_HDRLEN) */
#ifndef ETHER_HDRLEN 
#define ETHER_HDRLEN 14
#define SIZE_ETHERNET 14
#endif


typedef u_int tcp_seq;

ofstream out;

struct sniff_tcp {
	u_short th_sport;	/* source port */
	u_short th_dport;	/* destination port */
	tcp_seq th_seq;		/* sequence number */
	tcp_seq th_ack;		/* acknowledgement number */
	u_char th_offx2;	/* data offset, rsvd */
#define TH_OFF(th)	(((th)->th_offx2 & 0xf0) >> 4)
	u_char th_flags;
#define TH_FIN 0x01
#define TH_SYN 0x02
#define TH_RST 0x04
#define TH_PUSH 0x08
#define TH_ACK 0x10
#define TH_URG 0x20
#define TH_ECE 0x40
#define TH_CWR 0x80
#define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
	u_short th_win;		/* window */
	u_short th_sum;		/* checksum */
	u_short th_urp;		/* urgent pointer */
};



u_int16_t handle_ethernet
(u_char *args,const struct pcap_pkthdr* pkthdr,const u_char*
		packet);
u_char* handle_IP
(u_char *args,const struct pcap_pkthdr* pkthdr,const u_char*
		packet);

u_char* handle_IP_after_mean
(u_char *args,const struct pcap_pkthdr* pkthdr,const u_char*
		packet);



struct my_ip {
	u_int8_t	ip_vhl;		/* header length, version */
#define IP_V(ip)	(((ip)->ip_vhl & 0xf0) >> 4)
#define IP_HL(ip)	((ip)->ip_vhl & 0x0f)
	u_int8_t	ip_tos;		/* type of service */
	u_int16_t	ip_len;		/* total length */
	u_int16_t	ip_id;		/* identification */
	u_int16_t	ip_off;		/* fragment offset field */
#define	IP_DF 0x4000			/* dont fragment flag */
#define	IP_MF 0x2000			/* more fragments flag */
#define	IP_OFFMASK 0x1fff		/* mask for fragmenting bits */
	u_int8_t	ip_ttl;		/* time to live */
	u_int8_t	ip_p;		/* protocol */
	u_int16_t	ip_sum;		/* checksum */
	struct	in_addr ip_src,ip_dst;	/* source and dest address */
};



class portpair{
public: 
	long int src_port;
	long int dst_port;

	bool operator()(const class portpair s1,const  class portpair s2) const{
		if(s1.src_port != s2.src_port)
		{
			if(s1.src_port < s2.src_port)
			{
				return true;
			}
			else
				return false;
		}
		else{
			if(s1.dst_port < s2.dst_port)
			{
				return true;
			}
			else
				return false;
		}

	}
};



class stats{

public:
	long double volume;//(total pkts size/diff_time)
	double packet_rate;
	double avg_packet_size;//(total pkts size/count)
	int count;
	double stdevpacketsize;
	double stdevpacketrate;
	double interstdevpktrate;
	double interstdev;
	double inter_packet_rate;
	double pkt_rate_mean;
	map <int , int> hourlypacketcount;



	double stdevpacketvol;
	double interstdevpktvol;
	double pkt_vol_mean;
	double inter_packet_vol;
	map <int , long double> hourlypacketsize;

};



map <class portpair, class stats, class portpair> port_pairs;
map <class portpair, class stats, class portpair> ::iterator itr;
map <class portpair, class stats, class portpair> ::iterator p;
map <int, int> ::iterator hrpo;
map <int, long double> ::iterator hrsize;




void addToMap(class portpair tempp, double pkybytes){
	// port_pairs.insert ( pair<class portpair, int >(tempp,100) );

	itr = port_pairs.find(tempp);
	class stats st_temp;
	buffer_total+=pkybytes;
	currenthour =(int)diff_time/3600;

	if (itr == port_pairs.end()){
		st_temp.count=1;
		st_temp.packet_rate=(double)(st_temp.count/diff_time);
		st_temp.avg_packet_size=pkybytes;
		st_temp.volume=(long double)((st_temp.avg_packet_size*st_temp.count)/diff_time);
		st_temp.interstdev=0;
		st_temp.interstdevpktrate=0;
		st_temp.interstdevpktvol=0;
		st_temp.inter_packet_rate=0;
		st_temp.inter_packet_vol=0;
		hrpo = st_temp.hourlypacketcount.find(currenthour);
		if (hrpo == st_temp.hourlypacketcount.end()){
			st_temp.hourlypacketcount.insert(pair<int , int>(currenthour,1) );

		}
		else{
			hrpo->second+=1;
		}

		hrsize = st_temp.hourlypacketsize.find(currenthour);
		if (hrsize == st_temp.hourlypacketsize.end()){
			st_temp.hourlypacketsize.insert(pair<int , long double>(currenthour,(long double)pkybytes) );

		}
		else{
			hrsize->second+=pkybytes;
		}


		//st_temp.cumulative_packet_size=pkybytes;
		port_pairs.insert ( pair<class portpair, class stats >(tempp,st_temp) );

		//cout<<"yes"<<port_pairs[tempp];

	}
	else{
		itr->second.count+=1;
		itr->second.packet_rate=(double)(itr->second.count/diff_time);
		//itr->second.cumulative_packet_size+=itr->second.cumulative_packet_size;
		itr->second.avg_packet_size=  (((itr->second.count-1)*itr->second.avg_packet_size) + pkybytes)/itr->second.count;
		itr->second.volume=(long double)((itr->second.avg_packet_size*itr->second.count)/diff_time);
		itr->second.interstdev =0;

		hrpo = itr->second.hourlypacketcount.find(currenthour);
		if (hrpo == itr->second.hourlypacketcount.end()){
			itr->second.hourlypacketcount.insert ( pair<int, int >(currenthour,1) );

		}
		else{
			hrpo->second+=1;
		}

		hrsize = itr->second.hourlypacketsize.find(currenthour);
		if (hrsize == itr->second.hourlypacketsize.end()){
			itr->second.hourlypacketsize.insert(pair<int , long double>(currenthour, (long double)pkybytes) );

		}
		else{
			hrsize->second+=pkybytes;
		}



	}
}




void updateToMap(class portpair tempp, double pkybytes){
	// port_pairs.insert ( pair<class portpair, int >(tempp,100) );

	itr = port_pairs.find(tempp);
	if (itr == port_pairs.end()){

		//cout<<"BUGGY "<<endl;

	}
	else{

		itr->second.interstdev +=(pkybytes-itr->second.avg_packet_size)     *		(pkybytes-itr->second.avg_packet_size);
		/*if(itr->second.count==1){
			cout<<itr->second.interstdev<<" "<<endl;

		}*/
	}
}



void updatestats(){

	for(p = port_pairs.begin(); p != port_pairs.end(); p++) {

		p->second.packet_rate =(double)(p->second.count/diff_time);
		p->second.volume=(long double)((p->second.avg_packet_size*p->second.count)/diff_time);

	}
}




void iterateOverMap(){

	for(p = port_pairs.begin(); p != port_pairs.end(); p++) {
		cout << p->first.src_port ;
		cout << "\t" << p->first.dst_port ;
		cout << "\t" << p->second.packet_rate ;
		cout << "\t" << p->second.avg_packet_size;
		cout << "\t" << p->second.volume;
		cout << "\t" << p->second.count ;
		cout << "\t" << diff_time << endl ;
	}
}


void iterateOverMapsd(){

	for(p = port_pairs.begin(); p != port_pairs.end(); p++) {
		out << p->first.src_port ;
		out << "\t" << p->first.dst_port ;
		out << "\t" << p->second.packet_rate ;
		out << "\t" << p->second.avg_packet_size;
		out << "\t" << p->second.volume;
		out << "\t" << p->second.count ;
		out << "\t" << diff_time ;
		out << "\t" << p->second.stdevpacketsize ;
		out << "\t" << p->second.pkt_rate_mean ;
		out << "\t" << p->second.stdevpacketrate ;
		out << "\t" << p->second.pkt_vol_mean ;
		out << "\t" << p->second.stdevpacketvol<< endl ;

		/*if(p->second.count ==2){
			int t=1;
			for(int x=0;x<=currenthour;x++){
						hrsize = p->second.hourlypacketsize.find(x);
						if(hrsize->second!=0)
						cout<<hrsize->second<<endl;
						cout<<t<<"next"<<endl;
						t++;
		}
			break;
			}*/


	}
}


void totalstats(){

	cout << endl;
	cout << "Total packets "<< pkt_count << endl;
	cout << "Total time "<< (diff_time/3600) <<" hrs" << endl;
	cout << "Total pkt rate "<< (double)(pkt_count/diff_time) <<endl;
	cout << "Total Avg Bytes "<< (double)(buffer_total/pkt_count) << endl;
	cout << "Total Volume "<< (long double)(buffer_total/diff_time) <<  endl;

}



void stdevforrate(){

	for(p = port_pairs.begin(); p != port_pairs.end(); p++) {
		for(int x=0;x<=currenthour;x++){
			hrpo = p->second.hourlypacketcount.find(x);
			if (hrpo == p->second.hourlypacketcount.end()){

			}
			else{


				p->second.interstdevpktrate +=(hrpo->second-p->second.pkt_rate_mean)     *		(hrpo->second-p->second.pkt_rate_mean);
			}

		}

		p->second.stdevpacketrate=sqrt(p->second.interstdevpktrate/(currenthour+1));

		/*if(p->second.count==1){
cout<<p->second.stdevpacketrate <<"  "<<  p->second.interstdevpktrate  <<endl;
			 }*/
	}


}





void stdevforvol(){

	for(p = port_pairs.begin(); p != port_pairs.end(); p++) {
		for(int x=0;x<=currenthour;x++){
			hrsize = p->second.hourlypacketsize.find(x);
			if (hrsize == p->second.hourlypacketsize.end()){

			}
			else{


				p->second.interstdevpktvol +=(hrsize->second - p->second.pkt_vol_mean)     *		(hrsize->second - p->second.pkt_vol_mean);
			}

		}


		p->second.stdevpacketvol=sqrt(p->second.interstdevpktvol/(currenthour+1));

		/*if(p->second.count==1){
cout<<p->second.stdevpacketrate <<"  "<<  p->second.interstdevpktrate  <<endl;
			 }*/
	}


}


void correctallmaps(){

	for(p = port_pairs.begin(); p != port_pairs.end(); p++) {
		for(int x=0;x<=currenthour;x++){
			hrpo = p->second.hourlypacketcount.find(x);
			if (hrpo == p->second.hourlypacketcount.end()){
				p->second.hourlypacketcount.insert ( pair<int, int >(x,0) );

			}




			hrsize = p->second.hourlypacketsize.find(x);
			if (hrsize == p->second.hourlypacketsize.end()){
				p->second.hourlypacketsize.insert ( pair<int, long double >(x,(long double)0.00) );

			}



		}


	}


	for(p = port_pairs.begin(); p != port_pairs.end(); p++) {
		for(int x=0;x<=currenthour;x++){
			hrpo = p->second.hourlypacketcount.find(x);
			if (hrpo == p->second.hourlypacketcount.end()){

			}
			else{
				p->second.inter_packet_rate+=hrpo->second;

			}

			hrsize = p->second.hourlypacketsize.find(x);
			if (hrsize == p->second.hourlypacketsize.end()){

			}
			else{

				p->second.inter_packet_vol+=hrsize->second;
			}

		}

		p->second.pkt_rate_mean=p->second.inter_packet_rate/p->second.hourlypacketcount.size();
		p->second.pkt_vol_mean=p->second.inter_packet_vol/p->second.hourlypacketsize.size();

	}

}




/* just print a count every time we have a packet...                      
 * 
 *
 */
void my_callback(u_char *useless,const struct pcap_pkthdr* pkthdr,const u_char*	packet){

	static int count = 1;
	//fprintf(stdout,"%d, ",count);
	struct timeval tss=pkthdr->ts;

	if(count==1){
		start_time = (double)tss.tv_sec+(double)(tss.tv_usec/1000000);
	}
	current_time = (double)tss.tv_sec+(double)(tss.tv_usec/1000000);
	diff_time=current_time - start_time;
	if(diff_time==0.0){
		diff_time==0.0000001;
	}
	fflush(stdout);
	if(count%10==0){
		updatestats();
	}
	count++;
	pkt_count=count;

	// u_int16_t type = handle_ethernet(args,pkthdr,packet);

	u_int16_t type = handle_ethernet(useless,pkthdr,packet);

	if(type == ETHERTYPE_IP)
	{/* handle IP packet */
		handle_IP(useless,pkthdr,packet);
	}

}



void stdev(){

	for(p = port_pairs.begin(); p != port_pairs.end(); p++) {
		p->second.stdevpacketsize = sqrt( p->second.interstdev /p->second.count);
		/*if(p->second.count==1){
				cout<<p->second.stdevpacketsize <<"  "<<  p->second.interstdev  <<endl;
	    }*/

	}

}



void second_callback(u_char *useless,const struct pcap_pkthdr* pkthdr,const u_char*	packet){
	static int count = 1;
	//fprintf(stdout,"%d, ",count);
	// u_int16_t type = handle_ethernet(args,pkthdr,packet);

	u_int16_t type = handle_ethernet(useless,pkthdr,packet);

	if(type == ETHERTYPE_IP)
	{/* handle IP packet */
		handle_IP_after_mean(useless,pkthdr,packet);
	}

}



int main(int argc,char **argv){ 
	int i;
	char *dev; 
	char errbuf[PCAP_ERRBUF_SIZE];
	pcap_t* descr;
	const u_char *packet;
	struct pcap_pkthdr hdr;     /* pcap.h                    */
	struct ether_header *eptr;  /* net/ethernet.h            */
	struct bpf_program fp;      /* hold compiled program     */
	bpf_u_int32 maskp;          /* subnet mask               */
	bpf_u_int32 netp;           /* ip                        */
	buffer_total=0;

	if(argc != 4){ fprintf(stdout,"Usage: %s \"filter program\"\n"
			,argv[0]);return 0;}

	/* grab a device to peak into... */
	dev = pcap_lookupdev(errbuf);
	// if(dev == NULL)
	//{ fprintf(stderr,"%s\n",errbuf); exit(1); }

	out.open(argv[3]);
	/* ask pcap for the network address and mask of the device */
	pcap_lookupnet(dev,&netp,&maskp,errbuf);

	/* open device for reading this time lets set it in promiscuous
	 * mode so we can monitor traffic to another machine             */
	descr=pcap_open_offline(argv[2], errbuf);
	// descr = pcap_open_live(dev,BUFSIZ,1,-1,errbuf);
	if(descr == NULL)
	{ printf("pcap_open_live(): %s\n",errbuf); exit(1); }

	/* Lets try and compile the program.. non-optimized */
	if(pcap_compile(descr,&fp,argv[1],0,netp) == -1)
	{ fprintf(stderr,"Error calling pcap_compile\n"); exit(1); }

	/* set the compiled program as the filter */
	if(pcap_setfilter(descr,&fp) == -1)
	{ fprintf(stderr,"Error setting filter\n"); exit(1); }

	/* ... and loop */ 
	pcap_loop(descr,-1,my_callback,NULL);
	updatestats();
	//iterateOverMap();
	//totalstats();
	//cout<<" Doing It again ---" <<endl<<endl;

	descr=pcap_open_offline(argv[2], errbuf);
	// descr = pcap_open_live(dev,BUFSIZ,1,-1,errbuf);
	if(descr == NULL)
	{ printf("pcap_open_live(): %s\n",errbuf); exit(1); }

	/* Lets try and compile the program.. non-optimized */
	if(pcap_compile(descr,&fp,argv[1],0,netp) == -1)
	{ fprintf(stderr,"Error calling pcap_compile\n"); exit(1); }

	/* set the compiled program as the filter */
	if(pcap_setfilter(descr,&fp) == -1)
	{ fprintf(stderr,"Error setting filter\n"); exit(1); }

	/* ... and loop */
	pcap_loop(descr,-1,second_callback,NULL);

	stdev();
	correctallmaps();
	stdevforrate();

	stdevforvol();

	iterateOverMapsd();

	return 0;


}



u_int16_t handle_ethernet(u_char *args,const struct pcap_pkthdr* pkthdr,const u_char*	packet){
	u_int caplen = pkthdr->caplen;
	u_int length = pkthdr->len;
	struct ether_header *eptr;  /* net/ethernet.h */
	u_short ether_type;

	if (caplen < ETHER_HDRLEN)
	{
		//fprintf(stdout,"Packet length less than ethernet header length\n");
		return -1;
	}

	/* lets start with the ether header... */
	eptr = (struct ether_header *) packet;
	ether_type = ntohs(eptr->ether_type);

	/* Lets print SOURCE DEST TYPE LENGTH */
	//  fprintf(stdout,"ETH: ");
	//fprintf(stdout,"%s "
	//,ether_ntoa((struct ether_addr*)eptr->ether_shost));
	//fprintf(stdout,"%s "
	//,ether_ntoa((struct ether_addr*)eptr->ether_dhost));

	/* check to see if we have an ip packet */
	if (ether_type == ETHERTYPE_IP)
	{
		//  fprintf(stdout,"(IP)");
	}else  if (ether_type == ETHERTYPE_ARP)
	{
		//fprintf(stdout,"(ARP)");
	}else  if (eptr->ether_type == ETHERTYPE_REVARP)
	{
		//fprintf(stdout,"(RARP)");


	}else {
		//fprintf(stdout,"(?)");
	}
	//fprintf(stdout," %d\n",length);

	return ether_type;
}



u_char* handle_IP(u_char *args,const struct pcap_pkthdr* pkthdr,const u_char* packet){
	class portpair tempp;

	const struct sniff_tcp *tcp;
	u_int size_ip;
	const struct my_ip* ip;
	u_int length = pkthdr->len;
	u_int hlen,off,version;
	int i;
	int len;
	/* jump pass the ethernet header */
	ip = (struct my_ip*)(packet + sizeof(struct ether_header));
	size_ip = IP_HL(ip)*4;
	tcp = (struct sniff_tcp*)(packet + SIZE_ETHERNET + size_ip);

	tempp.src_port=ntohs(tcp->th_sport);
	tempp.dst_port=ntohs(tcp->th_dport);

	if( tempp.src_port>1024  ){
		tempp.src_port=-1;
	}

	if( tempp.dst_port>1024 ){
		tempp.dst_port=-1;
	}
	/*if(count==2){
		    			tempp.src_port=233;
		    			tempp.dst_port=66;
    }*/
	double pktbytes=pkthdr->len;
	addToMap(tempp,pktbytes);

	length -= sizeof(struct ether_header); 

	/* check to see we have a packet of valid length */
	if (length < sizeof(struct my_ip))
	{
		printf("truncated ip %d",length);
		return NULL;
	}

	len     = ntohs(ip->ip_len);
	hlen    = IP_HL(ip); /* header length */
	version = IP_V(ip);  /* ip version */

	/* check version */
	if(version != 4)
	{
		fprintf(stdout,"Unknown version %d\n",version);
		return NULL;
	}

	/* check header length */
	if(hlen < 5 )
	{
		fprintf(stdout,"bad-hlen %d \n",hlen);
	}

	/* see if we have as much packet as we should */
	if(length < len)
		printf("\ntruncated IP - %d bytes missing\n",len - length);

	/* Check to see if we have the first fragment */
	off = ntohs(ip->ip_off);
	if((off & 0x1fff) == 0 )/* aka no 1's in first 13 bits */
	{/* print SOURCE DESTINATION hlen version len offset */
		// fprintf(stdout,"IP: ");
		//fprintf(stdout,"%s ",	                inet_ntoa(ip->ip_src));
		//fprintf(stdout,"%s %d %d %d %d\n",		                inet_ntoa(ip->ip_dst), hlen,version,len,off);
	}

	return NULL;

}



u_char* handle_IP_after_mean(u_char *args,const struct pcap_pkthdr* pkthdr,const u_char* packet){
	class portpair tempp;

	const struct sniff_tcp *tcp;
	u_int size_ip;
	const struct my_ip* ip;
	u_int length = pkthdr->len;
	u_int hlen,off,version;
	int i;

	int len;

	/* jump pass the ethernet header */
	ip = (struct my_ip*)(packet + sizeof(struct ether_header));
	size_ip = IP_HL(ip)*4;
	tcp = (struct sniff_tcp*)(packet + SIZE_ETHERNET + size_ip);

	tempp.src_port=ntohs(tcp->th_sport);
	tempp.dst_port=ntohs(tcp->th_dport);

	if( tempp.src_port>1024  ){
		tempp.src_port=-1;
	}

	if( tempp.dst_port>1024 ){
		tempp.dst_port=-1;
	}
	/*if(count==2){
		    			tempp.src_port=233;
		    			tempp.dst_port=66;
	}*/
	double pktbytes=pkthdr->len;

	updateToMap(tempp,pktbytes);

	length -= sizeof(struct ether_header);

	/* check to see we have a packet of valid length */
	if (length < sizeof(struct my_ip))
	{
		printf("truncated ip %d",length);
		return NULL;
	}

	len     = ntohs(ip->ip_len);
	hlen    = IP_HL(ip); /* header length */
	version = IP_V(ip);  /* ip version */

	/* check version */
	if(version != 4)
	{
		fprintf(stdout,"Unknown version %d\n",version);
		return NULL;
	}

	/* check header length */
	if(hlen < 5 )
	{
		fprintf(stdout,"bad-hlen %d \n",hlen);
	}

	/* see if we have as much packet as we should */
	if(length < len)
		printf("\ntruncated IP - %d bytes missing\n",len - length);

	/* Check to see if we have the first fragment */
	off = ntohs(ip->ip_off);
	if((off & 0x1fff) == 0 )/* aka no 1's in first 13 bits */
	{/* print SOURCE DESTINATION hlen version len offset */
		// fprintf(stdout,"IP: ");
		//fprintf(stdout,"%s ",	                inet_ntoa(ip->ip_src));
		//fprintf(stdout,"%s %d %d %d %d\n",		                inet_ntoa(ip->ip_dst), hlen,version,len,off);
	}

	return NULL;

}

